#include "at_aithinker_combo.h"
#include <stdlib.h>
#include <string.h>

const char AT_AiCombo::REPLY_STRING_OK[3]    = "OK";
const char AT_AiCombo::REPLY_STRING_ERROR[6] = "ERROR";
const char AT_AiCombo::REPLY_STRING_EVENT[6] = "EVENT";

AT_AiCombo::AT_AiCombo(int (*send_data_fn)(const uint8_t * pdata, uint16_t size))
{
    ATDebugPrintf("test start\n");
    for (size_t i = 0; i < this->CMD_BUFFER_SIZE; i++)
    {
        this->push_idle_cmd(&(this->cmd_buffer[i]));
    }
    this->arg = this;
    this->mqtt_state = false;
    this->send_data_fn = send_data_fn;
}

int AT_AiCombo::cmd_send(const uint8_t *txdata, uint16_t size)
{
    if (this->send_data_fn != NULL)
        return this->send_data_fn(txdata, size);
    else return 0;
}

static bool cmd_parse_get_wmode(AT_AiCombo::wmode_t &wMode, const uint8_t *rxdata, uint16_t size)
{
/*
    +WMODE:<MODE>
    OK
    -----------------------
    <MODE>：Wi-Fi工作模式
    0：未初始化或者关闭Wi-Fi
    1：STA
    2：AP
    3：AP+STA
    -----------------------
    AT+WMODE?

    +WMODE:1
    OK
*/
    uint8_t *ptr;
    ptr = ATBase::data_find_sub(rxdata, size, (uint8_t*)"+WMODE:", 7);
    if (ptr == NULL)
    {
        return false;
    }
    ptr += 7;
    wMode = (AT_AiCombo::wmode_t)atoi((char*)ptr);
    return true;
}

static bool cmd_parse_get_ap_state(AT_AiCombo::ap_info_t &apInfo, const uint8_t *rxdata, uint16_t size)
{
/*
    +WJAP:<status>,<ssid>,<pwd>,<bssid>,<Security>,<MAC>,<ch>,<IP>,<gateway>
    Client Num: <client number>
    Client <id> MAC:
    OK
    -----------------------------------------------------------------------------
    AT+WJAP?

    +WJAP:3,ssid,pwd12345,d4:35:38:78:f4:06,WPA2 TKIP,b8:3d:fb:af:82:9d,1,192.168.0.115,192.168.0.1
    OK
*/
    uint8_t *ptr;
    int tmp;
    ptr = ATBase::data_find_sub(rxdata, size, (uint8_t*)"+WJAP:", 6);
    if (ptr == NULL)
    {
        return false;
    }
    ptr += 6;
    // status
    apInfo.status = (AT_AiCombo::ap_state_t)atoi((char*)ptr);
    ptr += 2; // skip ,
    // ssid
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.ssid)-1)
    {
        apInfo.ssid[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.ssid[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // password
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.pwd)-1)
    {
        apInfo.pwd[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.pwd[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // bssid
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.bssid)-1)
    {
        apInfo.bssid[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.bssid[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // security
    tmp = 0;
    while (ptr[tmp] != ',')
    {
        tmp++;
    }
    switch (*ptr)
    {
    case 'O':
        apInfo.security = 0;////TODO: AT_AiCombo::SECURITY_OPEN;
        break;
    case 'W':
        apInfo.security = 1;
        break;
        // if (strncmp((char*)ptr, "WPA2 TKIP", 9) == 0)
        //     apInfo.security =  AT_AiCombo::SECURITY_WPA2;
    default:
        break;
    }
    ptr += tmp + 1; // skip ,
    // MAC
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.mac)-1)
    {
        apInfo.mac[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.mac[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // channel
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.channel)-1)
    {
        apInfo.channel[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.channel[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // IP
    tmp = 0;
    while (ptr[tmp] != ',' && tmp < sizeof(apInfo.ip)-1)
    {
        apInfo.ip[tmp] = ptr[tmp];
        tmp++;
    }
    apInfo.ip[tmp] = '\0';
    ptr += tmp + 1; // skip ,
    // gateway
    tmp = 0;
    while (ptr[tmp] != '\r' && tmp < sizeof(apInfo.gateway)-1)
    {
        apInfo.gateway[tmp] = ptr[tmp];
        tmp++;
    }
    return true;
}

int AT_AiCombo::reply_check_ok(void * arg, const ATBase::at_cmd_t *pCmd, uint32_t events)
{
    AT_AiCombo *_this = (AT_AiCombo *)arg;
    switch (pCmd->id)
    {
    case AT_AiCombo::CMDID_RESET:
    case AT_AiCombo::CMDID_SET_WMODE:
    case AT_AiCombo::CMDID_CREATE_AP: //TODO:
    case AT_AiCombo::CMDID_MQTT_CFG_HOST:
    case AT_AiCombo::CMDID_MQTT_CFG_PORT:
    case AT_AiCombo::CMDID_MQTT_CFG_TYPE:
    case AT_AiCombo::CMDID_MQTT_CFG_CLIENTID:
    case AT_AiCombo::CMDID_MQTT_CFG_USERNAME:
    case AT_AiCombo::CMDID_MQTT_CFG_PWD:
    case AT_AiCombo::CMDID_MQTT_DISCONNECT:
    case AT_AiCombo::CMDID_MQTT_PUBLISH:
    case AT_AiCombo::CMDID_MQTT_SUBSCRIBE:
        if(events & ATBase::EVENT_MATCH)
        {
            ATDebugPrintf("[Combo] cid:%d Match, OK\r\n", pCmd->id);
        }
        break;
    case AT_AiCombo::CMDID_GET_WMODE:
        if(events & ATBase::EVENT_MATCH)
        {
            if (cmd_parse_get_wmode(_this->wmode, _this->rxData, _this->rxSize))
                ATDebugPrintf("[Combo] cid:%d, wmode:%d; OK\r\n", pCmd->id, _this->wmode);
            else
                ATDebugPrintf("[Combo] cid:%d, get wmode failed\r\n", pCmd->id);
        }
        break;
    case AT_AiCombo::CMDID_JOIN_AP:
        if(events & ATBase::EVENT_RECV)
        {
            if (data_find_sub(_this->rxData,
                              _this->rxSize,
                              (const uint8_t *)"WIFI_GOT_IP",
                              11) != NULL)
            {
                _this->ap_info.status = AT_AiCombo::APSTATE_OK;
                ATDebugPrintf("[Combo] cid:%d join ap success\r\n", pCmd->id);
                _this->next();
            }
            else
            {
                _this->rxSize = 0;
            }
        }
        break;
    case AT_AiCombo::CMDID_GET_APSTATE:
        if(events & ATBase::EVENT_MATCH)
        {         
            if (cmd_parse_get_ap_state(_this->ap_info, _this->rxData, _this->rxSize))
                ATDebugPrintf("[Combo] cid:%d, ssid[%s], ip[%s], MAC[%s], gateway[%s]; OK\r\n",
                    pCmd->id, _this->ap_info.ssid, _this->ap_info.ip,
                    _this->ap_info.mac, _this->ap_info.gateway);
            else
                ATDebugPrintf("[Combo] cid:%d, get wmode failed\r\n", pCmd->id);
        }
        break;
    case AT_AiCombo::CMDID_MQTT_CONNECT:
        if(events & ATBase::EVENT_RECV)
        {
            if (data_find_sub(_this->rxData,
                              _this->rxSize,
                              (const uint8_t *)"MQTT_CONNECT",
                              11) != NULL)
            {
                _this->mqtt_state = true;
                ATDebugPrintf("[Combo] cid:%d, mqtt connect success\r\n", pCmd->id);
                _this->next();
            }
            else
            {
                _this->rxSize = 0;
            }
        }
        break;
    default:
        break;
    }
    return 0;
}

void AT_AiCombo::receive_handle(const uint8_t * pdata, uint16_t size)
{
    if (pdata == NULL || size == 0)
        return;
    const uint8_t *ptr = pdata;
    int tmp = 0;
    // +EVENT:MQTT_SUB,<topic>,<size>,<payload>
    while (size > 20 && ptr[2] == '+' && this->mqtt_cfg != NULL && this->mqtt_cfg->topicsub_num != 0)
    {
        if (this->mqtt_cfg->subscribe_callback == NULL)
            break;
        ptr++;
        ptr = data_find_sub(ptr, size, (uint8_t *)"EVENT:MQTT_SUB", 14);
        if (ptr == NULL)
            break;
        ptr += 14 + 1; // skip EVENT:MQTT_SUB,
        for (size_t i = 0; i < this->mqtt_cfg->topicsub_num; i++)
        {
            tmp = strlen(this->mqtt_cfg->topicsub[i].pstr);
            if (tmp > size)
                continue;
            uint8_t* p;
            p = data_find_sub(ptr,
                              size,
                              (uint8_t *)this->mqtt_cfg->topicsub[i].pstr,
                              tmp);
            if (p == NULL)
                continue;
            p += tmp + 1; // skip topic,
            while (*p != ',' && *p != '\r') // skip size,
                p++;
            if (*p == '\r')
                continue;
            p++;
            this->mqtt_cfg->subscribe_callback(this->mqtt_cfg->topicsub[i].id, p, pdata - p);
            return;
        }
        break;
    }
    this->receive_callback(pdata, size);
}

bool AT_AiCombo::reset(void)
{
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    const char cmd_string[] = "AT+RST\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = this->reply_check_ok;   // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_RESET;                   // [4] cmd id
    pcmd->timeout = 400;                            // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = pcmd->cmd_size + sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
    this->push(pcmd);
    return true;
}


bool AT_AiCombo::set_wmode(wmode_t mode)
{
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    const char cmd_string[] = "AT+WMODE=%d,%d\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = this->reply_check_ok;   // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_SET_WMODE;                   // [4] cmd id
    pcmd->timeout = 400;                            // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = pcmd->cmd_size - 2 + sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
    this->push(pcmd, mode, ((int)mode >= WMODE_NONE_SAVE ? 1 : 0));
    return true;
}

bool AT_AiCombo::request_wmode(void)
{
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    const char cmd_string[] = "AT+WMODE?\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_GET_WMODE;               // [4] cmd id
    pcmd->timeout = 400;                            // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = pcmd->cmd_size + 10 + sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
    this->push(pcmd);
    return true;
}


bool AT_AiCombo::join_ap(const char *ssid, const char *pwd, const char *bssid)
{
    if (ssid == NULL || pwd == NULL)
        return false;
    const char cmd_string1[] = "AT+WJAP=\"%s\",\"%s\",\"%s\"\r\n";
    const char cmd_string2[] = "AT+WJAP=\"%s\",\"%s\"\r\n";
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    // AT+WJAP=<ssid>,<pwd>[,<bssid>]
    pcmd->reply = NULL;                             // [1] reply string
    pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_JOIN_AP;                 // [4] cmd id
    pcmd->timeout = 8000;                           // [5] timeout
    if (bssid != NULL && bssid[0] != '\0')
    {
        pcmd->cmd_size = sizeof(cmd_string1) - 1;   // [7] cmd size
        memcpy(pcmd->cmd, cmd_string1, pcmd->cmd_size);  // [8] cmd string
    }
    else 
    {
        pcmd->cmd_size = sizeof(cmd_string2) - 1;   // [7] cmd size
        memcpy(pcmd->cmd, cmd_string2, pcmd->cmd_size);  // [8] cmd string
    }
    pcmd->expect_size = pcmd->cmd_size +
                        strlen(ssid) +
                        strlen(pwd) +
                        (bssid != NULL ? strlen(bssid) : 0) +
                        sizeof("+EVENT:WIFI_CONNECT") +
                        sizeof(this->REPLY_STRING_OK) + 
                        sizeof("+EVENT:WIFI_GOT_IP"); // [6] expect size
    if (bssid != NULL)
        this->push(pcmd, ssid, pwd, bssid);
    else
        this->push(pcmd, ssid, pwd);
    return true;
}

bool AT_AiCombo::request_ap_state(void)
{
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    const char cmd_string[] = "AT+WJAP?\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_GET_APSTATE;             // [4] cmd id
    pcmd->timeout = 400;                            // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = pcmd->cmd_size + 95 + sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
    this->push(pcmd);
    return true;
}

bool AT_AiCombo::get_network_state(void)
{
    return this->ap_info.status == AT_AiCombo::APSTATE_OK;
}

bool AT_AiCombo::get_broadcast_ip(char result[16]) const
{
    int i;
    int dotCnt = 0;
    const char *ip = this->ap_info.gateway;
    for (i = 0; i < 15; i++)
    {
        result[i] = ip[i];
        if (ip[i] == '.')
        {
            dotCnt++;
            if (dotCnt == 3)
            {
                break;
            }
        }
    }
    if (dotCnt != 3 || i + 4 > 15)
    {
        return false;
    }
    result[i + 1] = '2';       // aaa.bbb.ccc.2
    result[i + 2] = '5';       // aaa.bbb.ccc.25
    result[i + 3] = '5';       // aaa.bbb.ccc.255
    result[i + 4] = '\0';      // aaa.bbb.ccc.255\0
    return true;
}

bool AT_AiCombo::mqtt_connect(mqtt_cfg_t *pMqttCfg)
{
    if (pMqttCfg == NULL)
    {
        return false;
    }
    if (this->get_idle_cmd_num() < 7)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    this->mqtt_cfg = pMqttCfg;
    at_cmd_t *pcmd;
    /*-------- config Host [AT+MQTT=1,104.208.69.92] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=1,%s\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_HOST;           // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size + 
                            strlen(pMqttCfg->host) +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->host);
    } while (0);

    /*-------- config Port [AT+MQTT=2,1883] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=2,%s\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_PORT;           // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size + 
                            strlen(pMqttCfg->port) +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->port);
    } while (0);

    /*-------- config connect type [AT+MQTT=3,1] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=3,1\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_TYPE;           // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd);
    } while (0);

    /*-------- config client id [AT+MQTT=4,client_id] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=4,%s\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_CLIENTID;       // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size +
                            strlen(pMqttCfg->client_id) +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->client_id);
    } while(0);

    /*-------- config username [AT+MQTT=5,username] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=5,%s\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_USERNAME;       // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size +
                            strlen(pMqttCfg->username) +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->username);
    } while (0);
    

    /*-------- config client pwd [AT+MQTT=6,password] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT=6,%s\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CFG_PWD;            // [4] cmd id
        pcmd->timeout = 200;                            // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size +
                            strlen(pMqttCfg->password) +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->password);
    } while (0);

    /*-------- connect mqtt [AT+MQTT] --------*/
    do {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTT\r\n";
        pcmd->reply = NULL;                             // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 1;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_CONNECT;            // [4] cmd id
        pcmd->timeout = 3000;                           // [5] timeout
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        pcmd->expect_size = pcmd->cmd_size + 
                            sizeof("+EVENT:MQTT_CONNECT") +
                            sizeof(this->REPLY_STRING_OK); // [6] expect size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);  // [8] cmd string
        this->push(pcmd, pMqttCfg->host);
    } while (0);

    return true;
}

bool AT_AiCombo::mqtt_disconnect(void)
{
    if (this->mqtt_state == false)
    {
        return false;
    }
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
        return false;
    }
    const char cmd_string[] = "AT+MQTTDISCONN\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_MQTT_DISCONNECT;         // [4] cmd id
    pcmd->timeout = 4000;                           // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = pcmd->cmd_size + sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);       // [8] cmd string
    this->push(pcmd);
    return true;
}

bool AT_AiCombo::mqtt_publish(const char *pTopic, const char *pPayload, uint8_t qos, bool retained)
{
    if (pTopic == NULL || pPayload == NULL)
    {
        return false;
    }
    at_cmd_t *pcmd = this->pop_idle_cmd();
    if (pcmd == NULL)
    {
        ATDebugPrintf("[Combo] Busy......\r\n");
    }
    const char cmd_string[] = "AT+MQTTPUB=\"%s\",%d,%d,\"%s\"\r\n";
    pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
    pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
    pcmd->retry = 0;                                // [3] retry times
    pcmd->id = this->CMDID_MQTT_PUBLISH;            // [4] cmd id
    pcmd->timeout = 300;                            // [5] timeout
    pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
    pcmd->expect_size = sizeof("AT+MQTTPUB=, , ,\r\n") +
                        sizeof(this->REPLY_STRING_OK); // [6] expect size
    memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);       // [8] cmd string
    this->push(pcmd, pTopic, qos, retained?1:0, pPayload);
    return true;
}

bool AT_AiCombo::mqtt_subscribe(void)
{
    if (this->mqtt_cfg->topicsub == NULL)
    {
        return false;
    }
    at_cmd_t *pcmd;
    if (this->get_idle_cmd_num() < this->mqtt_cfg->topicsub_num)
    {
        ATDebugPrintf("[Combo] cmds:%d/%d,Busy......\r\n", this->mqtt_cfg->topicsub_num, this->get_idle_cmd_num());
        return false;
    }
    for (size_t i = 0; i < this->mqtt_cfg->topicsub_num; i++)
    {
        pcmd = this->pop_idle_cmd();
        const char cmd_string[] = "AT+MQTTSUB=\"%s\",%d\r\n";
        pcmd->reply = this->REPLY_STRING_OK;            // [1] reply string
        pcmd->event_handle_fn = reply_check_ok;         // [2] event handle function
        pcmd->retry = 0;                                // [3] retry times
        pcmd->id = this->CMDID_MQTT_SUBSCRIBE;          // [4] cmd id
        pcmd->timeout = 300;                            // [5] timeout
        pcmd->expect_size = sizeof(this->REPLY_STRING_OK); // [6] expect size
        pcmd->cmd_size = sizeof(cmd_string) - 1;        // [7] cmd size
        memcpy(pcmd->cmd, cmd_string, pcmd->cmd_size);       // [8] cmd string
        this->push(pcmd, this->mqtt_cfg->topicsub[i].pstr, this->mqtt_cfg->topicsub[i].qos);
    }
    return true;
}
